namespace ScrumSprintMonitor.Implementation
{
	#region #using Directives

	using System;

	using ContinuousLinq.Reactive;

	using Infrastructure;

	#endregion

	public class TeamMemberRemainingWorkload : ReactiveObject,
	                                           ITeamMemberRemainingWorkload
	{
		static TeamMemberRemainingWorkload()
		{
			var dependsOn = Register<TeamMemberRemainingWorkload>();

			// TODO: Unit test changing SprintWork and PotentialSprintHours and their effect on SprintHours
			dependsOn.Call(me => me.VerifySprintHoursChanged()).OnChanged(me => me.PotentialSprintHours).OnChanged(me => me.SprintWork);
		}

		#region Implementation of TeamMemberRemainingWorkload

		private bool includesFreeBusyInformation;
		private double potentialSprintHours;
		private double previousSprintHours = double.NaN;
		private double sprintWork;
		private ITeamMemberInformation teamMemberInfo;
		private double unassignedSprintWork;
		private int workItemCount;

		public void Initialize(ITeamMemberInformation teamMemberInformation)
		{
			if (this.teamMemberInfo != null)
			{
				throw new InvalidOperationException();
			}

			this.teamMemberInfo = teamMemberInformation;
		}

		public int WorkItemCount
		{
			get { return this.workItemCount; }
			set
			{
				if (value != this.workItemCount)
				{
					this.workItemCount = value;
					OnPropertyChanged("WorkItemCount");
				}
			}
		}

		public double PotentialSprintHours
		{
			get { return this.potentialSprintHours; }
			set
			{
				if (Math.Round(value * 10.0) != Math.Round(this.potentialSprintHours * 10.0))
				{
					this.potentialSprintHours = value;
					OnPropertyChanged("PotentialSprintHours");
				}
			}
		}

		public double SprintHours
		{
			get
			{
				if (this.teamMemberInfo == null)
				{
					throw new InvalidOperationException("The Initialize method must be called prior to retrieving SprintHours!");
				}

				return (this.teamMemberInfo.DedicatedToSprint ? PotentialSprintHours : Math.Min(PotentialSprintHours, SprintWork));
			}
		}

		public bool IncludesFreeBusyInformation
		{
			get { return this.includesFreeBusyInformation; }
			set
			{
				if (value != this.includesFreeBusyInformation)
				{
					this.includesFreeBusyInformation = value;
					OnPropertyChanged("IncludesFreeBusyInformation");
				}
			}
		}

		public double SprintWork
		{
			get { return this.sprintWork; }
			set
			{
				if (value != this.sprintWork)
				{
					this.sprintWork = value;
					OnPropertyChanged("SprintWork");
				}
			}
		}

		public double UnassignedSprintWork
		{
			get { return this.unassignedSprintWork; }
			set
			{
				if (value != this.unassignedSprintWork)
				{
					this.unassignedSprintWork = value;
					OnPropertyChanged("UnassignedSprintWork");
				}
			}
		}

		#endregion

		#region Private Methods

		private void VerifySprintHoursChanged()
		{
			if (Math.Round(SprintHours * 10.0) != Math.Round(this.previousSprintHours * 10.0))
			{
				this.previousSprintHours = SprintHours;
				OnPropertyChanged("SprintHours");
			}
		}

		#endregion
	}
}